/*
 * The contents of this web application are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this web application except in compliance with 
 * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 * for the specific language governing rights and limitations under the License.
 * 
 * The Original Code is owned by and the Initial Developer of the Original Code is 
 * Composite A/S (Danish business reg.no. 21744409). All Rights Reserved
 * 
 * Section 11 of the License is EXPRESSLY amended to include a provision stating 
 * that any dispute, including but not limited to disputes related to the enforcement 
 * of the License, to which Composite A/S as owner of the Original Code, as Initial 
 * Developer or in any other role, becomes a part to shall be governed by Danish law 
 * and be initiated before the Copenhagen City Court ("K�benhavns Byret")            
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Workflow.Runtime;
using Composite.C1Console.Actions.Foundation;
using Composite.Core;
using Composite.Core.Extensions;
using Composite.Data;
using Composite.Data.Types;
using Composite.Core.Configuration;
using Composite.Core.Logging;
using Composite.C1Console.Security;
using Composite.C1Console.Users;
using Composite.C1Console.Workflow;


namespace Composite.C1Console.Actions
{
    /// <summary>    
    /// </summary>
    /// <exclude />
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 
    public static class FlowControllerFacade
    {
        private static readonly string LogTitle = typeof (FlowControllerFacade).Name;
        private static TimeSpan? _timeout = null;
        private static bool _initialized = false;
        private static object _lock = new object();



        /// <exclude />
        public static void Initialize()
        {
            WorkflowFacade.RunWhenInitialized(() =>
            {
                lock (_lock)
                {
                    if (_initialized == false)
                    {
                        WorkflowInstance workflowInstance = WorkflowFacade.CreateNewWorkflow(WorkflowFacade.GetWorkflowType("Composite.C1Console.Actions.Workflows.FlowInformationScavengerWorkflow"));
                        workflowInstance.Start();
                        WorkflowFacade.RunWorkflow(workflowInstance);

                        Log.LogVerbose(LogTitle, "Flow scavenger started");

                        _initialized = true;
                    }
                }
            });
        }



        /// <exclude />
        public static IFlowUiDefinition GetCurrentUiDefinition(FlowToken flowToken, FlowControllerServicesContainer flowControllerServicesContainer)
        {
            if (flowToken == null) throw new ArgumentNullException("flowToken");

            IFlowController flowExecutor = FlowControllerCache.GetFlowController(flowToken, flowControllerServicesContainer);

            IFlowUiDefinition flowResult = flowExecutor.GetCurrentUiDefinition(flowToken);

            if (flowResult == null)
            {
                flowResult = new NullFlowUiDefinition();
            }

            return flowResult;
        }



        /// <exclude />
        public static void CancelFlow(FlowToken flowToken)
        {
            if (flowToken == null) throw new ArgumentNullException("flowToken");

            IFlowController flowExecutor = FlowControllerCache.GetFlowController(flowToken, new FlowControllerServicesContainer());

            flowExecutor.CancelFlow(flowToken);
        }



        /// <exclude />
        public static void CancelFlowsByConsoleId(string consoleId)
        {
            List<string> serializedFlowTokens =
                (from f in DataFacade.GetData<IFlowInformation>()
                 where f.ConsoleId == consoleId
                 select f.SerializedFlowToken).ToList();

            foreach (string serializedFlowToken in serializedFlowTokens)
            {
                FlowToken flowToken = FlowTokenSerializer.Deserialize(serializedFlowToken);

                CancelFlow(flowToken);
            }
        }



        /// <exclude />
        public static IEnumerable<FlowToken> GetFlowTokensByUsername(string username)
        {
            List<string> serializedFlowTokens =
                (from f in DataFacade.GetData<IFlowInformation>()
                 where f.Username == username
                 select f.SerializedFlowToken).ToList();

            foreach (string serializedFlowToken in serializedFlowTokens)
            {
                FlowToken flowToken = FlowTokenSerializer.Deserialize(serializedFlowToken);

                yield return flowToken;
            }
        }



        /// <exclude />
        public static IEnumerable<string> GetConsoleIdsByUsername(string username)
        {
            List<string> consoleIds =
                (from f in DataFacade.GetData<IFlowInformation>()
                 where f.Username == username
                 select f.ConsoleId).ToList();

            return consoleIds;
        }



        /// <exclude />
        public static void FlowComplete(FlowToken flowToken)
        {
            UnregisterFlowInformation(flowToken);
        }



        internal static void RegisterNewFlowInformation(FlowToken flowToken, EntityToken entityToken, ActionToken actionToken, string consoleId)
        {
            IFlowInformation flowInformation = DataFacade.BuildNew<IFlowInformation>();
            flowInformation.Id = Guid.NewGuid();
            flowInformation.Username = UserSettings.Username;
            flowInformation.ConsoleId = consoleId;
            flowInformation.SerializedFlowToken = FlowTokenSerializer.Serialize(flowToken);
            flowInformation.SerializedEntityToken = EntityTokenSerializer.Serialize(entityToken);
            flowInformation.SerializedActionToken = ActionTokenSerializer.Serialize(actionToken);
            flowInformation.TimeStamp = DateTime.Now;

            DataFacade.AddNew<IFlowInformation>(flowInformation);
        }



        internal static void UnregisterFlowInformation(FlowToken flowToken)
        {
            string serializedFlowToken = FlowTokenSerializer.Serialize(flowToken);

            DataFacade.Delete<IFlowInformation>(f => f.SerializedFlowToken == serializedFlowToken);
        }



        /// <exclude />
        public static void Scavenge()
        {
            Log.LogVerbose(LogTitle, "Starting scavenger run");

            List<IFlowInformation> flowInformations = DataFacade.GetData<IFlowInformation>().ToList();

            // NOTE: Low performance implementation
            foreach (IFlowInformation flowInformation in flowInformations)
            {
                TimeSpan timeSpan = DateTime.Now - flowInformation.TimeStamp;
                if (timeSpan > Timeout)
                {
                    FlowToken flowToken = null;
                    string flowTokenStr;

                    try
                    {
                        flowToken = FlowTokenSerializer.Deserialize(flowInformation.SerializedFlowToken);

                        flowTokenStr = flowToken.ToString();
                    }
                    catch(Exception)
                    {
                        flowTokenStr = flowInformation.SerializedFlowToken ?? string.Empty;
                        if(flowTokenStr.Length > 200)
                        {
                            flowTokenStr = flowTokenStr.Substring(0, 200);
                        }
                    }

                    Log.LogVerbose(LogTitle, "Scavenging flow started by username '{0}', flow = '{1}'".FormatWith(flowInformation.Username, flowTokenStr));

                    DataFacade.Delete<IFlowInformation>(flowInformation);

                    if (flowToken != null)
                    {
                        FlowControllerFacade.CancelFlow(flowToken);
                    }
                }
            }
        }



        private static TimeSpan Timeout
        {
            get
            {
                if (_timeout.HasValue == false)
                {
                    _timeout = GlobalSettingsFacade.WorkflowTimeout;
                }

                return _timeout.Value;
            }
        }
    }
}
